/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework
 * Version 2.10
 * 
 * Data Actor Base Class
 *      Provides a base definition for classes which directly interact with
 * data sources.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.Data;
using System.Data.Common;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.IO;
using Microsoft.SqlServer.Server;
using System.Net;
using System.Net.Sockets;
using System.Data.SqlClient;
using Adaptive.Win32;
using Adaptive.Foundation.Properties;

namespace Adaptive.Foundation.Data
{
    /// <summary>
    /// Provides a base definition for classes which directly interact with
    /// data sources.
    /// </summary>
    public abstract class DataActorBase : DisposableObjectBase
    {
        /*--------------------------------------------------------------------------------
         * Public Event Definitions
         *------------------------------------------------------------------------------*/
        #region Public Event Definitions
        /// <summary>
        /// Occurs when a connection to a data source is established.
        /// </summary>
        public event ConnectionEventHandler ConnectionSuccess;
        /// <summary>
        /// Occurs when a connection to a data source fails.
        /// </summary>
        public event ExceptionEventHandler ConnectionFailure;
        /// <summary>
        /// Occurs when a data exception is caught.
        /// </summary>
        public event DataExceptionEventHandler DataException;
        /// <summary>
        /// Occurs when a general, non-data exception is caught.
        /// </summary>
        public event ExceptionEventHandler ExceptionCaught;
        #endregion

        /*--------------------------------------------------------------------------------
		 * Private Member Declarations
		 *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Connection string defining the data source.
        /// </summary>
        private string _connectString = string.Empty;
        /// <summary>
        /// The data provider instance used to generate platform-specific data objects.
        /// </summary>
        private IDataProvider _provider;
        /// <summary>
        /// User impersonation session.
        /// </summary>
        private ImpersonationSession _session;

        private Socket _backgroundConnection;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
		 *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        /// <param name="provider">
        /// A <see cref="IDataProvider"/> instance used to generate the platform-specific
        /// database objects.
        /// </param>
        protected DataActorBase(IDataProvider provider)
            : base()
        {
            if (provider == null)
                throw new ArgumentNullException(Resources.ProviderArgumentName);

            _provider = provider;
            _session = new ImpersonationSession();
        }
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="provider">
        /// A <see cref="IDataProvider"/> instance used to generate the platform-specific
        /// database objects.
        /// </param>
        /// <param name="connectionString">
        /// The connection string used to define the data source to connect to.
        /// </param>
        protected DataActorBase(IDataProvider provider, string connectionString)
            : base()
        {
            if (provider == null)
                throw new ArgumentNullException(Resources.ProviderArgumentName);

            _provider = provider;
            _connectString = connectionString;
            _session = new ImpersonationSession();
        }
        /// <summary>
        /// Deallocates internally allocated objectts.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                if (_session != null)
                    _session.Dispose();
            }

            _session = null;
            _provider = null;
            _connectString = null;
            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
	     * Public Properties
		 *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets or sets the connection string used to connect to a data source.
        /// </summary>
        /// <value>
        /// A database connection string value.
        /// </value>
        public string ConnectString
        {
            get { return _connectString; }
            set { _connectString = value; }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Methods / Functions / Overrides
		 *------------------------------------------------------------------------------*/
        #region Protected Methods / Functions / Overrides
        /// <summary>
        /// Attempts to connect to a remote database instance.
        /// </summary>
        /// <returns>
        /// An <see cref="IDbConnection"/> instance to use, if successful; otherwise,
        /// returns <b>null</b>.
        /// </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected virtual IDbConnection TryConnect()
        {
            DbConnectionStringBuilder connectParser = null;         //Connection string parser/builder.
            IDbConnection connectionInstance = null;                //Connection instance.
            string serverName = null;                                   //Server.

            connectParser = _provider.CreateConnectStringBuilder(_connectString);
            serverName = (string)connectParser["Server"];

            OpenBackgroundConnection(serverName);

            connectionInstance = _provider.CreateConnection(_connectString);

            try
            {
                connectionInstance.Open();
                OnConnectionSuccess(new ConnectionEventArgs(connectionInstance));
            }
            catch (Exception ex)
            {
                System.Threading.Thread.Sleep(100);
                System.Windows.Forms.Application.DoEvents();
                OnConnectionFailure(new ExceptionEventArgs(ex));
                connectionInstance.Dispose();
                connectionInstance = null;
                GC.Collect();
            }

            if (connectionInstance == null)
            {
                System.Threading.Thread.Sleep(100);
                System.Windows.Forms.Application.DoEvents();
                connectionInstance = TryConnectImpersonated();
                GC.Collect();
            }

            return connectionInstance;
        }
        /// <summary>
        /// Attempts to connect to a remote database instance using impersonation.
        /// </summary>
        /// <returns>
        /// An <see cref="IDbConnection"/> instance to use, if successful; otherwise,
        /// returns <b>null</b>.
        /// </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected virtual IDbConnection TryConnectImpersonated()
        {
            IDbConnection connectionInstance = null;

            connectionInstance = _provider.CreateConnection(_connectString);
            try
            {
                connectionInstance.Open();
                OnConnectionSuccess(new ConnectionEventArgs(connectionInstance));
            }
            catch (Exception ex)
            {
                OnConnectionFailure(new ExceptionEventArgs(ex));
                connectionInstance.Dispose();
                connectionInstance = null;
            }

            return connectionInstance;
        }
        /// <summary>
        /// Closes the supplied connection instance.
        /// </summary>
        /// <param name="connection">
        /// The instance to close.
        /// </param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected virtual void TryClose(IDbConnection connection)
        {
            if (connection != null)
            {
                try
                {
                    connection.Close();
                    connection.Dispose();
                }
                catch(Exception ex)
                {
                    OnExceptionCaught(new ExceptionEventArgs(ex));
                }
            }
            CloseBackgroundConnection();
            _session.CloseSession();
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Event Methods / Event Methods
		 *------------------------------------------------------------------------------*/
        #region Protected Event Methods / Event Methods
        /// <summary>
        /// Raises the <see cref="ConnectionSuccess"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="ConnectionEventArgs"/> event arguments instance 
        /// containing the data related to the connection.
        /// </param>
        protected virtual void OnConnectionSuccess(ConnectionEventArgs e)
        {
            if (ConnectionSuccess != null)
                ConnectionSuccess(this, e);
        }
        /// <summary>
        /// Raises the <see cref="ConnectionFailure"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="ExceptionEventArgs"/> event arguments instance 
        /// containing the data related to the connection failure.
        /// </param>
        protected virtual void OnConnectionFailure(ExceptionEventArgs e)
        {
            if (ConnectionFailure != null)
                ConnectionFailure(this, e);
        }
        /// <summary>
        /// Raises the <see cref="DataException"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="DataExceptionEventArgs"/> event arguments instance 
        /// containing the data related to the data exception.
        /// </param>
        protected virtual void OnDataException(DataExceptionEventArgs e)
        {
            if (DataException != null)
                DataException(this, e);
        }
        /// <summary>
        /// Raises the <see cref="E:OnExceptionCaught"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Adaptive.Foundation.ExceptionEventArgs"/> instance containing the event data.</param>
        protected virtual void OnExceptionCaught(ExceptionEventArgs e)
        {
            if (ExceptionCaught != null)
                ExceptionCaught(this, e);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
		 *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Gets a dataset from the remote data source.
        /// </summary>
        /// <param name="sql">
        /// The SQL query text to use to create the dataset.
        /// </param>
        /// <returns>
        /// A <see cref="DataSet"/> instance, if successful; otherwise, returns <b>null</b>.
        /// </returns>
        public DataSet GetDataset(string sql)
        {
            DataSet returnValue = null;                 //Return value.
            IDbCommand command = null;                  //Command to execute.
            IDataAdapter adapter = null;                //Data adapter instance.
            IDbConnection connection = null;            //Connection instance.
            
            connection = TryConnect();
            if (connection != null)
            {
                //Create the command and adapter instances.
                command = _provider.CreateCommand(sql, connection);
                adapter = _provider.CreateAdapter(command);
                returnValue = new DataSet();
                returnValue.Locale = System.Threading.Thread.CurrentThread.CurrentCulture;
 
                try
                {
                    adapter.Fill(returnValue);
                }
                catch (DataException ex)
                {
                    OnDataException(new DataExceptionEventArgs(ex));
                    returnValue.Dispose();
                    returnValue = null;
                }
                catch (Exception ex)
                {
                    OnExceptionCaught(new ExceptionEventArgs(ex));
                }

                //Close objects.
                ((IDisposable)adapter).Dispose();
                command.Dispose();

                //Close connection.
                TryClose(connection);
                connection.Dispose();
            }

            return returnValue;
        }
        /// <summary>
        /// Gets a data table from the remote data source.
        /// </summary>
        /// <param name="sql">
        /// The SQL query text to use to create the data table.
        /// </param>
        /// <returns>
        /// A <see cref="DataTable"/> instance, if successful; otherwise, returns <b>null</b>.
        /// </returns>
        public DataTable GetTable(string sql)
        {
            DataSet dataContainer = null;           //Temporary container instance.
            DataTable returnValue = null;           //Data to return.

            //Create the data set.
            dataContainer = GetDataset(sql);
            if (dataContainer != null)
            {
                //Clone the original table.
                if (dataContainer.Tables.Count > 0)
                    returnValue = dataContainer.Tables[0].Clone();

                //Clear memory.
                dataContainer.Clear();
                dataContainer.Dispose();
            }

            return returnValue;
        }
        /// <summary>
        /// Gets a data reader from the database.
        /// </summary>
        /// <param name="sql">
        /// The SQL query text to use to create the reader instance.
        /// </param>
        /// <returns>
        /// An <see cref="IDataReader"/> instance, if successful; otherwise, returns <b>null</b>.
        /// </returns>
        public IDataReader GetReader(string sql)
        {
            IDataReader returnValue = null;             //Return value.
            IDbCommand command = null;                  //Command to execute.
            IDbConnection connection = null;            //Connection instance.

            connection = TryConnect();
            if (connection != null)
            {
                //Create the command and adapter instances.
                command = _provider.CreateCommand(sql, connection);

                try
                {
                    returnValue = command.ExecuteReader();
                }
                catch (DataException ex)
                {
                    OnDataException(new DataExceptionEventArgs(ex));
                    returnValue.Close();
                    returnValue.Dispose();
                    returnValue = null;
                }
                catch (Exception ex)
                {
                    OnExceptionCaught(new ExceptionEventArgs(ex));
                }

                //Close objects.
                command.Dispose();

                //Do not close connection - reader requires connection to be left open.
            }
            return returnValue;
        }
        /// <summary>
        /// Executes the specified SQL query.
        /// </summary>
        /// <param name="sql">
        /// The SQL Query text to be executed.
        /// </param>
        /// <returns>
        /// The number of rows affected.
        /// </returns>
        public int ExecuteSql(string sql)
        {
            int returnValue = 0;                        //Return value.
            IDbCommand command = null;                  //Command to execute.
            IDbConnection connection = null;            //Connection instance.
            IDbTransaction transaction = null;          //Transaction container.


            if ((sql == null) || (sql.Length == 0))
                throw new ArgumentNullException(Resources.SqlArgumentName);

            connection = TryConnect();
            if (connection != null)
            {
                //Create the transaction, command and adapter instances.
                transaction = _provider.CreateTransactionInstance(connection);
                command = _provider.CreateCommand(sql, connection);
                command.Transaction = transaction;

                try
                {
                    returnValue = command.ExecuteNonQuery();
                    transaction.Commit();
                }
                catch (DataException ex)
                {
                    OnDataException(new DataExceptionEventArgs(ex));
                    transaction.Rollback();
                }
                catch (Exception ex)
                {
                    OnExceptionCaught(new ExceptionEventArgs(ex));
                    transaction.Rollback();
                }

                //Close objects.
                transaction.Dispose();
                command.Dispose();

                //Close the connection.
                TryClose(connection);
            }

            return returnValue;
        }
        /// <summary>
        /// Executes the specified stored procedure object.
        /// </summary>
        /// <param name="procedure">
        /// A <see cref="StoredProcedureBase"/> instance to be executed.
        /// </param>
        /// <returns>
        /// The <see cref="StoredProcedureResult"/> instance describing the result of 
        /// execution.
        /// </returns>
        public StoredProcedureResult ExecuteStoredProcedure(StoredProcedureBase procedure)
        {
            StoredProcedureResult returnValue = null;           //Return value.
            IDbConnection connectionInstance = null;            //Connection instance.

            if (procedure == null)
                throw new ArgumentNullException(Resources.ProcedureArgumentName);

            //Try to connect.
            connectionInstance = TryConnect();
            if (connectionInstance != null)
            {
                returnValue = procedure.Execute(connectionInstance);
                TryClose(connectionInstance);
            }
            else
            {
                returnValue = new StoredProcedureResult();
                returnValue.Success = false;
                returnValue.LastException = new DataException(Resources.DatabaseConnectFailMsg);
            }

            return returnValue;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool TestConnectivity()
        {
            IDbConnection connection = null;
            bool returnValue = false;

            connection = TryConnect();
            if (connection != null)
            {
                returnValue = ((connection.State == ConnectionState.Connecting) || (connection.State == ConnectionState.Open));
                TryClose(connection);
            }

            connection = null;
            return returnValue;
        }

        private void OpenBackgroundConnection(string serverName)
        {
            IPHostEntry hostEntry = null;
            IPEndPoint newEndPoint;

            try
            {
                hostEntry = Dns.GetHostEntry(serverName);
            }
            catch
            {
            }

            if ((hostEntry != null) && (hostEntry.AddressList.Length > 0))
            {
                try
                {
                    _backgroundConnection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
                    newEndPoint = new IPEndPoint(hostEntry.AddressList[0], 1433);

                    _backgroundConnection.Connect(newEndPoint);
                }
                catch
                {
                    _backgroundConnection.Close();
                    _backgroundConnection = null;
                }
            }
            GC.Collect();

        }
        private void CloseBackgroundConnection()
        {
            if (_backgroundConnection != null)
            {
                try
                {
                    _backgroundConnection.Disconnect(false);
                }
                catch
                { }

                try
                {
                    _backgroundConnection.Close();
                }
                catch
                { }

                _backgroundConnection = null;
            }
            GC.Collect();
        }
        #endregion
    }
}